package com.jh.paymentchannel.pojo.profit;

import cn.jh.common.core.json.JsonKt;
import cn.jh.common.core.json.JsonParseException;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.type.MapType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.google.common.base.MoreObjects;
import com.google.common.collect.BoundType;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Range;
import com.jh.paymentchannel.payment.OrderType;
import com.jh.paymentchannel.pojo.PaymentOrderStatistic;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;

import java.io.Serializable;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Function;

import static com.google.common.base.Preconditions.checkArgument;


/**
 * {@link PaymentOrderStatistic} 分润计算的利率配置数据。
 * <p/>
 * 分润利率数据从 {@code /v1.0/user/third_pay/rate/brand} 接口中获取。
 *
 * @author Fuchun
 * @since 1.0
 */
public class PmtProfitRate {

    private static final Function<OrderType, List<ProfitRateMarshal>> INITIAL_PPM_LIST;
    private static final Class<List<Map<String, Object>>> LIST_MAP_CLASS;
    private static final PmtProfitRate DEFAULT_PROFIT_RATE;

    static {
        TypeFactory factory = TypeFactory.defaultInstance();
        MapType mapType = factory.constructMapType(Map.class, String.class, Object.class);
        @SuppressWarnings("unchecked")
        Class<List<Map<String, Object>>> listMapClass = (Class<List<Map<String, Object>>>)
                factory.constructCollectionType(List.class, mapType).getRawClass();
        LIST_MAP_CLASS = listMapClass;
        INITIAL_PPM_LIST = input -> new ArrayList<>();
        DEFAULT_PROFIT_RATE = initDefaultProfitRate();
    }

    @NotNull
    private static PmtProfitRate initDefaultProfitRate() {
        BigDecimal boundary = new BigDecimal("300");
        BigDecimal atLeastRate = new BigDecimal("0.006");
        BigDecimal greaterThanRate = new BigDecimal("0.0022");
        ProfitRate atLeast = new ProfitRate(atLeastRate, Range.atLeast(boundary));
        ProfitRate greaterThan = new ProfitRate(greaterThanRate, Range.greaterThan(boundary));
        ImmutableList<ProfitRate> profitRates = ImmutableList.of(atLeast, greaterThan);
        ImmutableMap<OrderType, List<ProfitRate>> dataMap = ImmutableMap.of(
                OrderType.ALIPAY, profitRates,
                OrderType.WECHAT, profitRates
        );
        return new PmtProfitRate(dataMap);
    }

    @Contract(pure = true)
    public static PmtProfitRate getDefaultProfitRate() {
        return DEFAULT_PROFIT_RATE;
    }

    @NotNull
    @Contract("null -> fail")
    public static PmtProfitRate fromJson(String json) {
        checkArgument(json != null && !json.isEmpty(),
                "The given `json` must be not null or empty.");
        Map<OrderType, List<ProfitRateMarshal>> marshalMap;
        Map<OrderType, List<Map<String, Object>>> marshalSourceMap;
        try {
            marshalSourceMap = JsonKt.readToMap(json, OrderType.class, LIST_MAP_CLASS);

            marshalMap = new LinkedHashMap<>(marshalSourceMap.size());
            for (Map.Entry<OrderType, List<Map<String, Object>>> entry : marshalSourceMap.entrySet()) {
                for (Map<String, Object> map : entry.getValue()) {
                    List<ProfitRateMarshal> ppmList = marshalMap.computeIfAbsent(entry.getKey(), INITIAL_PPM_LIST);
                    ppmList.add(JsonKt.convertValue(map, ProfitRateMarshal.class));
                }
            }
        } catch (JsonParseException ex) {
            throw new IllegalArgumentException(String.format(
                    "The given `json` (%s) format error.", json));
        }
        Map<OrderType, List<ProfitRate>> profitMap = new HashMap<>(marshalMap.size());
        for (Map.Entry<OrderType, List<ProfitRateMarshal>> entry : marshalMap.entrySet()) {
            List<ProfitRateMarshal> prmList = entry.getValue();
            List<ProfitRate> profitRates = new ArrayList<>(prmList.size());
            for (ProfitRateMarshal prm : prmList) {
                Range<BigDecimal> range;
                BoundType lowerBoundType, upperBoundType;
                if (prm.lower != null) {
                    lowerBoundType = (prm.lowerClosed != null && prm.lowerClosed) ? BoundType.CLOSED : BoundType.OPEN;
                    if (prm.upper != null) {
                        upperBoundType = (prm.upperClosed != null && prm.upperClosed) ? BoundType.CLOSED : BoundType.OPEN;
                        range = Range.range(prm.lower, lowerBoundType, prm.upper, upperBoundType);
                    } else {
                        range = Range.downTo(prm.lower, lowerBoundType);
                    }
                } else if (prm.upper != null) {
                    upperBoundType = (prm.upperClosed != null && prm.upperClosed) ? BoundType.CLOSED : BoundType.OPEN;
                    range = Range.upTo(prm.upper, upperBoundType);
                } else {
                    continue;
                }
                profitRates.add(new ProfitRate(prm.getRate(), range));
            }
            profitMap.put(entry.getKey(), profitRates);
        }
        return new PmtProfitRate(profitMap);
    }

    private final Map<OrderType, List<ProfitRate>> profitRateMap;

    private PmtProfitRate(Map<OrderType, List<ProfitRate>> profitRateMap) {
        this.profitRateMap = profitRateMap;
        new TypeReference<OrderType>() {};
    }

    @NotNull
    List<ProfitRate> getProfitRates(OrderType orderType) {
        return profitRateMap.getOrDefault(orderType, ImmutableList.of());
    }

    /**
     * 获取给定的订单类型的交易金额所在范围的分润比率。
     * 如果给定的金额没有匹配的分润金额区间，则返回 {@link BigDecimal#ZERO}，表示不会获得分润。
     *
     * @param orderType 订单类型（渠道）。
     * @param amount 交易金额。
     * @return 返回给定的订单类型的交易金额所在范围的分润比率。
     */
    public BigDecimal getRate(OrderType orderType, BigDecimal amount) {
        if (orderType == null || amount == null) {
            return BigDecimal.ZERO;
        }
        List<ProfitRate> rates = profitRateMap.getOrDefault(orderType, ImmutableList.of());
        if (rates.isEmpty()) return BigDecimal.ZERO;
        for (ProfitRate rate : rates) {
            if (rate.getRange().contains(amount)) {
                return rate.getRate();
            }
        }
        return BigDecimal.ZERO;
    }

    public String toJson() {
        return JsonKt.toJSONString(profitRateMap);
    }

    public static class ProfitRate implements Serializable {

        private final BigDecimal rate;
        private final Range<BigDecimal> range;

        public ProfitRate(BigDecimal rate, Range<BigDecimal> range) {
            this.rate = rate;
            this.range = range;
        }

        public BigDecimal getRate() {
            return rate;
        }

        public Range<BigDecimal> getRange() {
            return range;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            ProfitRate that = (ProfitRate) o;
            return Objects.equals(rate, that.rate) &&
                    Objects.equals(range, that.range);
        }

        @Override
        public int hashCode() {
            return Objects.hash(rate, range);
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                    .add("rate", rate)
                    .add("range", range)
                    .toString();
        }
    }

    public static class ProfitRateMarshal implements Serializable {
        private final BigDecimal rate;
        private final BigDecimal lower;
        private final Boolean lowerClosed;
        private final BigDecimal upper;
        private final Boolean upperClosed;

        @JsonCreator
        public ProfitRateMarshal(
                @JsonProperty("rate") BigDecimal rate,
                @JsonProperty("lower") BigDecimal lower,
                @JsonProperty("lowerClosed") Boolean lowerClosed,
                @JsonProperty("upper") BigDecimal upper,
                @JsonProperty("upperClosed") Boolean upperClosed) {
            this.rate = rate;
            this.lower = lower;
            this.lowerClosed = lowerClosed;
            this.upper = upper;
            this.upperClosed = upperClosed;
        }

        public BigDecimal getRate() {
            return rate;
        }

        public BigDecimal getLower() {
            return lower;
        }

        public Boolean getLowerClosed() {
            return lowerClosed;
        }

        public BigDecimal getUpper() {
            return upper;
        }

        public Boolean getUpperClosed() {
            return upperClosed;
        }

        @Override
        public String toString() {
            return MoreObjects.toStringHelper(this)
                    .add("rate", rate)
                    .add("lower", lower)
                    .add("lowerClosed", lowerClosed)
                    .add("upper", upper)
                    .add("upperClosed", upperClosed)
                    .toString();
        }
    }
}
